package com.mspbots.core.report.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mspbots.core.AbstractBaseServiceImpl;
import com.mspbots.core.report.mapper.HealthReportMapper;
import com.mspbots.core.report.model.*;
import com.mspbots.core.report.service.HealthReportService;
import com.mspbots.core.script.model.TenantScript;
import com.mspbots.core.wise.model.TenantConfiguration;
import com.mspbots.core.wise.model.TenantExceptionLog;
import com.mspbots.core.wise.model.TenantUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;


@Log4j2
@Service
@RequiredArgsConstructor
public class HealthReportServiceImpl extends AbstractBaseServiceImpl<HealthReportMapper, TenantHealthReportDetail> implements HealthReportService {

    private final HealthReportMapper healthReportMapper;

    @Override
    public IPage<TenantHealthReportDetail> listTenant(Page<TenantHealthReportDetail> page, HealthReportDTO query) {
        IPage<TenantHealthReportDetail> healthList = healthReportMapper.listTenant(page, query);
        return healthList;
    }

    @Override
    public TenantHealthReportDetail queryTenantHealth(Long tenantId) {
        TenantHealthReportDetail tenantHealth = healthReportMapper.queryTenantHealth(tenantId);
        tenantHealthCheck(tenantHealth);
        healthReportMapper.updateHealth(tenantHealth);
        return tenantHealth;
    }

    @Override
    public List<TenantHealthReportDetail> checkListTenant() {
        return healthReportMapper.checkListTenant();
    }

    @Override
    public Boolean tenantHealthCheck(TenantHealthReportDetail health) {

        //teamsSetting
        ObjectNode teamsJson = teamsSettingCheck(health.getTenantId());
        log.debug("teamsSetting:" + teamsJson.toString().length());
        health.setTeamsSetting(teamsJson);
        health.setTeamsError(teamsJson.get("teamsError").asBoolean());

        //connectWiseSetting
        ObjectNode cwJson = connectWiseSettingCheck(health.getTenantId());
        log.debug("connectWiseSetting:" + cwJson.toString().length());
        health.setConnectWiseSetting(cwJson);
        health.setCwError(cwJson.get("cwError").asBoolean());

        //users
        ObjectNode usersJson = usersCheck(health.getTenantId());
        log.debug("users:" + usersJson.toString().length());
        health.setUsers(usersJson);
        health.setUserError(usersJson.get("userError").asBoolean());


        //botSetting
        ObjectNode botJson = botSettingCheck(health.getTenantId());
        log.debug("botSetting:" + botJson.toString().length());
        health.setBotSetting(botJson);
        health.setBotError(botJson.get("botError").asBoolean());

        //messageSetting
        ObjectNode messageJson = messageSettingCheck(health.getTenantId());
        log.debug("messageSetting:" + messageJson.toString().length());
        health.setMessageSetting(messageJson);
        health.setMessageError(messageJson.get("messageError").asBoolean());

        //isError is total error
        Boolean isError = true;

        if (!teamsJson.get("teamsError").asBoolean()
                && !cwJson.get("cwError").asBoolean()
                && !usersJson.get("userError").asBoolean()
                && !botJson.get("botError").asBoolean()
                && !messageJson.get("messageError").asBoolean()) {
            isError = false;
        }
        health.setIsError(isError);
        return isError;
    }

    @Override
    public ObjectNode teamsSettingCheck(Long tenantId) {
        ObjectNode teamsJson = this.objectMapper.createObjectNode();
        teamsJson.put("teamsError", false);
        HealthReportDTO dto = new HealthReportDTO();
        dto.setTenantId(tenantId);
        dto.setType("SYNC-TEAMS-USER");
        TenantExceptionLog teamsLog = healthReportMapper.selectLastTeamsLog(dto);
        ObjectNode lastSyncTeamsUserJson = this.objectMapper.createObjectNode();
        if (teamsLog != null) {
            String syncTimeEntryDateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(teamsLog.getLogTime()).replace(" ", "T") + "Z";
            lastSyncTeamsUserJson.put("logtime", syncTimeEntryDateStr);
            lastSyncTeamsUserJson.put("status", teamsLog.getStatus());
            lastSyncTeamsUserJson.put("msg", teamsLog.getMsg());
        } else {
            teamsJson.put("teamsError", true);
            lastSyncTeamsUserJson.put("msg", "teams user not sync!!");
        }
        teamsJson.set("lastSyncTeamsUser", lastSyncTeamsUserJson);
        return teamsJson;
    }

    @Override
    public ObjectNode connectWiseSettingCheck(Long tenantId) {
        ObjectNode cwJson = this.objectMapper.createObjectNode();
        cwJson.put("cwError", false);
        HealthReportDTO dto = new HealthReportDTO();
        dto.setTenantId(tenantId);
        List<TenantConfiguration> tenantConfigurations = healthReportMapper.selectTenantKeySetting(dto);
        ObjectNode keySettingObj = this.objectMapper.createObjectNode();
        if (CollectionUtils.isEmpty(tenantConfigurations)) {
            ObjectNode appIdNode = this.objectMapper.createObjectNode();
            ObjectNode siteNode = this.objectMapper.createObjectNode();
            ObjectNode publicKeyNode = this.objectMapper.createObjectNode();
            ObjectNode privateKeyNode = this.objectMapper.createObjectNode();
            appIdNode.put("isEmpty", true).put("value", "This value is not set.");
            siteNode.put("isEmpty", true).put("value", "This value is not set.");
            publicKeyNode.put("isEmpty", true).put("value", "This value is not set.");
            privateKeyNode.put("isEmpty", true).put("value", "This value is not set.");
            keySettingObj.put("cwKeySettingFlag", false);
            keySettingObj.put("message", "appId,site,publicKey,privateKey is empty.");
            keySettingObj.set("appId", appIdNode);
            keySettingObj.set("site", siteNode);
            keySettingObj.set("publicKey", publicKeyNode);
            keySettingObj.set("privateKey", privateKeyNode);
            cwJson.put("cwError", true);
        } else {
            StringBuffer buffer = new StringBuffer();
            tenantConfigurations.parallelStream().forEach(tenantConfiguration -> {
                ObjectNode keyObj = this.objectMapper.createObjectNode();
                String key = tenantConfiguration.getKeyword().substring(tenantConfiguration.getKeyword().lastIndexOf(".") + 1, tenantConfiguration.getKeyword().length());
                if (tenantConfiguration.getEnable() && !StringUtils.isEmpty(tenantConfiguration.getKeywordValue())) {
                    keyObj.put("isEmpty", false);
                    keyObj.put("value", tenantConfiguration.getKeywordValue());
                } else {
                    buffer.append(key + ",");
                    keyObj.put("isEmpty", true);
                    keyObj.put("value", tenantConfiguration.getKeywordValue());
                    cwJson.put("cwError", true);
                }
                keySettingObj.put(key, keyObj);
            });
            if (StringUtils.isEmpty(buffer.toString())) {
                keySettingObj.put("cwKeySettingFlag", true);
                keySettingObj.put("message", "appId,site,publicKey,privateKey is not empty.");
            } else {
                keySettingObj.put("cwKeySettingFlag", false);
                keySettingObj.put("message", buffer.toString() + " is empty.");
            }
        }

        cwJson.put("cwKeySettingObj", keySettingObj);

        List<TenantExceptionLog> authList = healthReportMapper.selectTenantAuthority(dto);
        if (CollectionUtils.isEmpty(authList)) {
            cwJson.put("cwError", true);
        }
        ObjectNode authObj = this.objectMapper.createObjectNode();
        authList.stream().forEach(auth -> {
            if (StringUtils.isEmpty(auth.getMsg())) {
                authObj.put(auth.getType(), true);
            } else if (!auth.getMsg().toLowerCase().contains("permission")) {
                authObj.put(auth.getType(), true);
            } else {
                cwJson.put("cwError", true);
            }
        });
        cwJson.put("cwAuth", authObj);

        dto.setType("SYNC-WISE-USER");

        TenantExceptionLog lastSyncUser = healthReportMapper.selectLastSync(dto);
        ObjectNode lastSyncUserJson = this.objectMapper.createObjectNode();
        if (lastSyncUser != null) {
            String syncUserDateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(lastSyncUser.getLogTime()).replace(" ", "T") + "Z";
            lastSyncUserJson.put("logtime", syncUserDateStr);
            lastSyncUserJson.put("status", lastSyncUser.getStatus());
            lastSyncUserJson.put("msg", lastSyncUser.getMsg());
        } else {
            lastSyncUserJson.put("msg", "please check tenant status and key Setting value!");
        }
        cwJson.put("lastSyncUser", lastSyncUserJson);

        dto.setType("SYNC-WISE-TIMES");

        TenantExceptionLog lastSyncTimeEntry = healthReportMapper.selectLastSync(dto);
        ObjectNode lastSyncTimeEntryJson = this.objectMapper.createObjectNode();
        if (lastSyncTimeEntry != null) {
            String syncTimeEntryDateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(lastSyncTimeEntry.getLogTime()).replace(" ", "T") + "Z";
            lastSyncTimeEntryJson.put("logtime", syncTimeEntryDateStr);
            lastSyncTimeEntryJson.put("status", lastSyncTimeEntry.getStatus());
            lastSyncTimeEntryJson.put("msg", lastSyncTimeEntry.getMsg());
        } else {
            lastSyncTimeEntryJson.put("msg", "please check tenant status and key Setting value!");
        }
        cwJson.put("lastSyncTimeEntry", lastSyncTimeEntryJson);


        dto.setType("SYNC-WISE-BOARDS");
        TenantExceptionLog lastSyncboards = healthReportMapper.selectLastSync(dto);
        ObjectNode lastSyncboardsJson = this.objectMapper.createObjectNode();
        if (lastSyncboards != null) {
            if (lastSyncboards.getLogTime() != null) {
                String dateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(lastSyncboards.getLogTime()).replace(" ", "T") + "Z";
                lastSyncboardsJson.put("logtime", dateStr);
            }
            lastSyncboardsJson.put("status", lastSyncboards.getStatus());
            lastSyncboardsJson.put("msg", lastSyncboards.getMsg());
        } else {
            lastSyncboardsJson.put("msg", "please check tenant status and key Setting  value!");
        }
        cwJson.put("lastSyncboards", lastSyncboardsJson);
        return cwJson;
    }

    @Override
    public ObjectNode usersCheck(Long tenantId) {
        ObjectNode usersJson = this.objectMapper.createObjectNode();
        HealthReportDTO dto = new HealthReportDTO();
        dto.setTenantId(tenantId);
        usersJson.put("userError", false);
        List<User> adminUsers = healthReportMapper.selectTenantAdmin(dto);

        ArrayNode adminUserArrJson = this.objectMapper.createArrayNode();
        adminUsers.forEach(user -> {
            ObjectNode adminUserJson = this.objectMapper.createObjectNode();
            adminUserJson.put("name", user.getUsername());
            adminUserArrJson.add(adminUserJson);
        });
        usersJson.put("adminUsers", adminUserArrJson);


        ObjectNode usersNotMappingJson = this.objectMapper.createObjectNode();
        Integer countMapping = healthReportMapper.selectCountTenantUserMapping(dto);
        usersNotMappingJson.put("countMapping", countMapping);

        Integer count = healthReportMapper.selectCountTenantUser(dto);
        usersNotMappingJson.put("count", count);
        if (!count.equals(countMapping) || count == 0) {
            usersJson.put("userError", true);
        }

        List<TenantUser> tenantUsers = healthReportMapper.selectTenantUserNotMapping(dto);
        if (!CollectionUtils.isEmpty(tenantUsers)) {
            usersJson.put("userError", true);
        }

        ArrayNode usersNotMappingArrJson = this.objectMapper.createArrayNode();
        tenantUsers.stream().forEach(user -> {
            ObjectNode usersNotMappingUserJson = this.objectMapper.createObjectNode();
            usersNotMappingUserJson.put("name", user.getIdentifier());
            usersNotMappingArrJson.add(usersNotMappingUserJson);
        });
        usersNotMappingJson.put("tenantUserNotMapping", usersNotMappingArrJson);
        usersJson.put("usersNotMapping", usersNotMappingJson);


        List<TenantUser> tenantUsersNoManager = healthReportMapper.selectTenantUserNoManager(dto);
        if (!CollectionUtils.isEmpty(tenantUsersNoManager)) {
            usersJson.put("userError", true);
        }

        ArrayNode tenantUsersNameNoManagerArrJson = this.objectMapper.createArrayNode();
        tenantUsersNoManager.stream().forEach(user -> {
            ObjectNode tenantUsersNameNoManagerJson = this.objectMapper.createObjectNode();
            tenantUsersNameNoManagerJson.put("name", user.getIdentifier());
            tenantUsersNameNoManagerArrJson.add(tenantUsersNameNoManagerJson);
        });
        usersJson.put("usersNoManager", tenantUsersNameNoManagerArrJson);


        List<TenantUser> tenantUsersNoWorkSchedule = healthReportMapper.selectTenantUserNoWorkSchedule(dto);
        if (!CollectionUtils.isEmpty(tenantUsersNoWorkSchedule)) {
            usersJson.put("userError", true);
        }
        ArrayNode tenantUsersNoWorkScheduleArrJson = this.objectMapper.createArrayNode();
        tenantUsersNoWorkSchedule.stream().forEach(user -> {
            ObjectNode tenantUsersNoWorkScheduleJson = this.objectMapper.createObjectNode();
            tenantUsersNoWorkScheduleJson.put("name", user.getIdentifier());
            tenantUsersNoWorkScheduleArrJson.add(tenantUsersNoWorkScheduleJson);
        });
        usersJson.put("usersNoWorkSchedule", tenantUsersNoWorkScheduleArrJson);
        return usersJson;


    }


    @Override
    public ObjectNode botSettingCheck(Long tenantId) {
        ObjectNode botJson = this.objectMapper.createObjectNode();
        botJson.put("botError", false);
        ArrayNode botArrJson = this.objectMapper.createArrayNode();
        HealthReportDTO dto = new HealthReportDTO();
        dto.setTenantId(tenantId);
        List<TenantScript> tenantUsersbots = healthReportMapper.selectTenantBot(dto);
        tenantUsersbots.stream().forEach(tenantScript -> {
            ObjectNode scriptJson = this.objectMapper.createObjectNode();
            scriptJson.put("name", tenantScript.getName());
            scriptJson.put("scope", tenantScript.getScope());
            scriptJson.put("triggerevent", tenantScript.getTriggerEvent());


            if (StringUtils.isEmpty(tenantScript.getSendEscalationStr()) || tenantScript.getSendEscalationStr().equals("false")) {
                tenantScript.setSendEscalation(false);
            } else {
                tenantScript.setSendEscalation(true);
            }
            if (StringUtils.isEmpty(tenantScript.getSendStr()) || tenantScript.getSendStr().equals("false")) {
                tenantScript.setSend(false);
                botJson.put("botError", true);
            } else {
                tenantScript.setSend(true);
            }

            scriptJson.put("send", tenantScript.getSend());
            scriptJson.put("sendEscalation", tenantScript.getSendEscalation());
            botArrJson.add(scriptJson);
        });
        botJson.set("botArrJson", botArrJson);
        return botJson;

    }

    @Override
    public ObjectNode messageSettingCheck(Long tenantId) {
        ObjectNode messageJson = this.objectMapper.createObjectNode();
        messageJson.put("messageError", false);
        HealthReportDTO dto = new HealthReportDTO();
        dto.setTenantId(tenantId);
        Integer countMessages = healthReportMapper.selectCountMessages(dto);
        Integer userNoReceived = healthReportMapper.selectCountUserNoReceived(dto);
        Integer userSendMessage = healthReportMapper.selectCountUserSendMessage(dto);
        Integer userHasReceived = healthReportMapper.selectCountUserHasReceived(dto);
        List<HealthReportCount> botMessages = healthReportMapper.selectCountBotMessages(dto);

        ArrayNode botMessageArrJson = this.objectMapper.createArrayNode();
        if (!CollectionUtils.isEmpty(botMessages)) {
            botMessages.parallelStream().forEach(healthReportCount -> {
                ObjectNode botMessageJson = this.objectMapper.createObjectNode();
                botMessageJson.put("name", healthReportCount.getName());
                botMessageJson.put("count", healthReportCount.getCount());
                botMessageArrJson.add(botMessageJson);
            });
        }

        if (!countMessages.equals(userHasReceived) || countMessages == 0) {
            messageJson.put("messageError", true);
        }
        messageJson.put("countMessages", countMessages);
        messageJson.put("userNoReceived", userNoReceived);
        messageJson.put("userSendMessage", userSendMessage);
        messageJson.put("userHasReceived", userHasReceived);
        messageJson.set("botMessages", botMessageArrJson);
        return messageJson;
    }

    @Override
    public List<TenantConfiguration> selectTenantKeySetting(HealthReportDTO query) {
        return healthReportMapper.selectTenantKeySetting(query);
    }

    @Override
    public List<TenantExceptionLog> selectTenantAuthority(HealthReportDTO query) {
        List<TenantExceptionLog> list = healthReportMapper.selectTenantAuthority(query);

        Collections.sort(list, (o1, o2) -> {
            if (o2.getMsg().toLowerCase().contains("error")) {
                return 1;
            }
            if (o2.getMsg().toLowerCase().contains("success")) {
                return -1;
            }
            return 0;
        });
        return list;
    }

    @Override
    public TenantExceptionLog selectLastSync(HealthReportDTO query) {
        return healthReportMapper.selectLastSync(query);
    }

    @Override
    public List<User> selectTenantAdmin(HealthReportDTO query) {
        return healthReportMapper.selectTenantAdmin(query);
    }

    @Override
    public Integer selectCountTenantUserMapping(HealthReportDTO query) {
        return healthReportMapper.selectCountTenantUserMapping(query);
    }

    @Override
    public Integer selectCountTenantUser(HealthReportDTO query) {
        return healthReportMapper.selectCountTenantUser(query);
    }

    @Override
    public List<TenantUser> selectTenantUserNotMapping(HealthReportDTO query) {
        return healthReportMapper.selectTenantUserNotMapping(query);
    }

    @Override
    public List<TenantUser> selectTenantUserNoManager(HealthReportDTO query) {
        return healthReportMapper.selectTenantUserNoManager(query);
    }

    @Override
    public List<TenantUser> selectTenantUserNoWorkSchedule(HealthReportDTO query) {
        return healthReportMapper.selectTenantUserNoWorkSchedule(query);
    }

    @Override
    public List<TenantScript> selectTenantBot(HealthReportDTO query) {
        return healthReportMapper.selectTenantBot(query);
    }


    @Override
    public Integer selectCountMessages(HealthReportDTO query) {
        return healthReportMapper.selectCountMessages(query);
    }


    @Override
    public Integer selectCountMessagesOnReceived(HealthReportDTO query) {
        return healthReportMapper.selectCountUserHasReceived(query);
    }


    @Override
    public Long listTenantCount(HealthReportDTO query) {
        return healthReportMapper.listTenantCount(query);
    }


    @Override
    public Integer selectCountUserNoReceived(HealthReportDTO query) {
        return healthReportMapper.selectCountUserNoReceived(query);
    }


    @Override
    public Integer selectCountUserSendMessage(HealthReportDTO query) {
        return healthReportMapper.selectCountUserSendMessage(query);
    }


    @Override
    public Integer selectCountUserHasReceived(HealthReportDTO query) {
        return healthReportMapper.selectCountUserHasReceived(query);
    }


    @Override
    public List<HealthReportCount> selectCountBotMessages(HealthReportDTO query) {
        return healthReportMapper.selectCountBotMessages(query);
    }

    @Override
    public TenantExceptionLog selectLastTeamsLog(HealthReportDTO query) {
        return healthReportMapper.selectLastTeamsLog(query);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateHealth(TenantHealthReportDetail entity) {
        Integer result;
        List<TenantHealthReport> healths = healthReportMapper.selectHealthReport(entity.getTenantId());
        if (healths.size() > 0) {
            result = healthReportMapper.updateHealth(entity);
        } else {
            result = healthReportMapper.insertHealth(entity);
        }
        return result > 0;
    }
}
